package com.seelyn.rhea.integrate.spring;

import com.seelyn.rhea.mvc.internal.enums.HttpMethod;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;

import java.util.ArrayList;
import java.util.List;

/**
 * @author linfeng
 * @since 2016/1/28
 */
public class RouteFactoryBeanDefinitionParser extends AbstractBeanDefinitionParser {

    private List<String> routePaths = new ArrayList<>();
    private List<String> resolverPaths = new ArrayList<>();

    @Override
    protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {


        ManagedList<BeanMetadataElement> routeList = new ManagedList<>();
        ManagedList<BeanMetadataElement> resolverList = new ManagedList<>();
        ManagedList<BeanMetadataElement> filterList = new ManagedList<>();

        String factoryRef = "springRouteFactory";
        BeanDefinitionBuilder springRouteFactory = BeanDefinitionBuilder
                .rootBeanDefinition(SpringRouteFactory.class);
        parserContext.getRegistry().registerBeanDefinition(factoryRef,
                springRouteFactory.getBeanDefinition());

        BeanDefinitionBuilder internalRoute = null;
        List<Element> routeElements = DomUtils.getChildElementsByTagName(element, "route");
        for (Element routeElement : routeElements) {

            String routeMethod = routeElement.getAttribute("method");
            String routePath = routeElement.getAttribute("path");
            String routeRef = routeElement.getAttribute("ref");

            if (!StringUtils.hasText(routeMethod)) {
                parserContext.getReaderContext().error("route method must be provided", routeElement);
            }
            if (!StringUtils.hasText(routePath)) {
                parserContext.getReaderContext().error("route path must be provided", routeElement);
            }
            if (!StringUtils.hasText(routeRef)) {
                parserContext.getReaderContext().error("route ref must be provided", routeElement);
            }
            if (routePaths.contains(routePath)) {
                parserContext.getReaderContext().error("There are multiple path corresponding to route", routeElement);
            } else {
                routePaths.add(routePath);
            }

            internalRoute = BeanDefinitionBuilder
                    .rootBeanDefinition(InternalRouteImpl.class);
            internalRoute.addConstructorArgValue(routePath);
            internalRoute.addConstructorArgValue(routeMethod);
            internalRoute.addConstructorArgReference(routeRef);

            routeList.add(internalRoute.getBeanDefinition());

        }

        BeanDefinitionBuilder internalFilter = null;
        List<Element> filterElements = DomUtils.getChildElementsByTagName(element, "filter");
        for (Element filterElement : filterElements) {

            String filterMethod = filterElement.getAttribute("method");
            String filterPath = filterElement.getAttribute("path");
            String filterRef = filterElement.getAttribute("ref");

            if (!StringUtils.hasText(filterMethod)) {
                parserContext.getReaderContext().error("filter method must be provided", filterElement);
            }
            if (!StringUtils.hasText(filterPath)) {
                parserContext.getReaderContext().error("filter path must be provided", filterElement);
            }
            if (!StringUtils.hasText(filterRef)) {
                parserContext.getReaderContext().error("filter ref must be provided", filterElement);
            }

            // Create a bean definition speculatively for the auth endpoint
            internalFilter = BeanDefinitionBuilder
                    .rootBeanDefinition(InternalFilterImpl.class);
            internalFilter.addConstructorArgValue(filterPath);
            internalFilter.addConstructorArgValue(filterMethod);
            internalFilter.addConstructorArgReference(filterRef);

            filterList.add(internalFilter.getBeanDefinition());

        }

        BeanDefinitionBuilder internalResolver = null;
        List<Element> resolverElements = DomUtils.getChildElementsByTagName(element, "route");
        for (Element resolverElement : resolverElements) {

            String resolverMethod = resolverElement.getAttribute("method");
            String resolverPath = resolverElement.getAttribute("path");
            String resolverRef = resolverElement.getAttribute("ref");


            if (!StringUtils.hasText(resolverPath)) {
                parserContext.getReaderContext().error("route path must be provided", resolverElement);
            }
            if (!StringUtils.hasText(resolverRef)) {
                parserContext.getReaderContext().error("route ref must be provided", resolverElement);
            }
            if (resolverPaths.contains(resolverPath)) {
                parserContext.getReaderContext().error("There are multiple path corresponding to resolver", resolverElement);
            } else {
                resolverPaths.add(resolverPath);
            }

            if (!StringUtils.hasText(resolverMethod)) {

                for (HttpMethod method: HttpMethod.values()) {
                    internalResolver = BeanDefinitionBuilder
                            .rootBeanDefinition(InternalResolverImpl.class);
                    internalResolver.addConstructorArgValue(resolverPath);
                    internalResolver.addConstructorArgValue(method.name());
                    internalResolver.addConstructorArgReference(resolverRef);

                    resolverList.add(internalResolver.getBeanDefinition());
                }

            } else {

                internalResolver = BeanDefinitionBuilder
                        .rootBeanDefinition(InternalResolverImpl.class);
                internalResolver.addConstructorArgValue(resolverPath);
                internalResolver.addConstructorArgValue(resolverMethod);
                internalResolver.addConstructorArgReference(resolverRef);

                resolverList.add(internalResolver.getBeanDefinition());
            }

        }

        springRouteFactory.addPropertyValue("routeList", routeList);
        springRouteFactory.addPropertyValue("filterList", filterList);

        springRouteFactory.setInitMethodName("initRoute");

        return null;
    }


}
